#include "Simulation.h"
#include <DirectXMath.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <chrono>
#include <cassert>
#include <atlstr.h>

#define MOUSE_SENSIBILITY (0.001f)
#define MOVEMENT_SPEED (15)
#define USE_TIMER 1

Simulation::Simulation( GraphicsRenderer& renderer, ClassicSimulationInput& input, std::vector<Snapshot>& snapshots) :
	m_graphicsRenderer(renderer),
	m_cameraFree(DirectX::XMFLOAT3(0.f, 6.f, -6.f), DirectX::XMFLOAT3(0.0f, 0.0f, 0.f)),
	m_cameraSimulationView(DirectX::XMVectorSet(0.f, 0.f, 0.f, 1.f), DirectX::XMQuaternionIdentity()),
	m_input(input),
	m_snapshots(snapshots),
	m_kochanekBartelsCurve(snapshots),
	m_slerpInterpolator(snapshots),
	m_simulationMode(FreeMode),
	m_lastSimulationStart(render_clock_t::now()),
	m_rayTriangleIntersections()
{
	m_rayPosition[0] = DirectX::XMFLOAT3(0.f, 0.f, 0.f);
	m_rayPosition[1] = DirectX::XMFLOAT3(0.f, 0.f, 0.f);
	m_graphicsRenderer.SetCamera(&m_cameraFree);
	m_KDTree = new KdTree(3);
	m_KDTreeRootNode = new KdTree::KD_NODE_t();
	m_BuildKdTree();
}

int Simulation::Run( void )
{
	render_time_t simulationStart, lastFrame;
	simulationStart = lastFrame = render_clock_t::now();
	MSG messageQueue = {0};
	while( WM_QUIT != messageQueue.message )
	{
		if( PeekMessage( &messageQueue, nullptr, 0, 0, PM_REMOVE ) )
		{
			TranslateMessage( &messageQueue );
			DispatchMessage( &messageQueue );
		}
		else
		{
#if(USE_TIMER == 1)
			render_time_t thisFrame = render_clock_t::now();
			render_duration_t actualRenderDuration = thisFrame - lastFrame;
			double deltaTime = actualRenderDuration.count();
#else
			double deltaTime = 0.0003;
#endif
			assert(m_simulationMode == FreeMode || m_simulationMode == ViewMode);
			if(m_input.GetShouldToggleAntiAliasing())
			{
				m_graphicsRenderer.ToggleAntiAliasing();
			}
			if(m_input.GetShouldStepAntiAliasingMode())
			{
				m_graphicsRenderer.StepAntiAliasingMode();
			}
			if(m_input.GetShouldSwitchRasterizer())
			{
				m_graphicsRenderer.SwitchRasterizer();
			}
			if(m_input.GetShouldSwitchUseBumpMap())
			{
				m_graphicsRenderer.SwitchUseBumpMap();
			}
			m_graphicsRenderer.ApplyBumpinessChange(m_input.GetBumpinessManipulator());
			if(m_simulationMode == FreeMode)
			{
				float movement = static_cast<float>(deltaTime * MOVEMENT_SPEED);
				DirectX::XMINT2 mouseMovement = m_input.GetMouseMovement();
				DirectX::XMFLOAT3 deltaCameraRotation( mouseMovement.y * MOUSE_SENSIBILITY, mouseMovement.x * MOUSE_SENSIBILITY, 0.f);
				DirectX::XMFLOAT3 cameraMovement(m_input.GetHorizontalMovement() * movement, 0.f, m_input.GetForwardMovement() * movement);
				DirectX::XMFLOAT3 cameraRotationIndependentMovement(0.f, m_input.GetVerticalMovement() * movement, 0.f);
				m_cameraFree.AddRotation(deltaCameraRotation);
				m_cameraFree.Move(cameraMovement);
				m_cameraFree.MoveRotationIndependent(cameraRotationIndependentMovement);
				if(m_input.GetShouldClearSnapshots())
				{
					m_snapshots.clear();
				}
				for(int i = 0; (i < m_input.GetNumSnapshotCommands()); ++i)
				{
					m_snapshots.push_back(m_cameraFree.GetSnapshot());
				}
				if(m_input.GetShouldGoToViewMode())
				{
					m_SwitchToViewMode();
				}
			}
			else//m_simulationMode == ViewMode
			{
				render_duration_t actualSimulationDuration = thisFrame - m_lastSimulationStart;
				double deltaSimulationTime = actualSimulationDuration.count();
				DirectX::XMVECTOR actualposition = m_kochanekBartelsCurve.GetLocation(deltaSimulationTime);
				DirectX::XMVECTOR actualrotation = m_slerpInterpolator.GetSlerpedRotation(deltaSimulationTime);
				m_cameraSimulationView.SetTranslation(actualposition);
				m_cameraSimulationView.SetRotation(actualrotation);
				if(m_input.GetShouldGoToFreeMode())
				{
					m_SwitchToFreeMode();
				}
			}
			if(m_input.GetShouldSendRay())
			{
				m_UpdateRayBasedOnCamera();
				m_HighLightFirstRayTriangleIntersection();
			}
			if(m_input.GetShouldToggleShowKdTree())
			{
				m_graphicsRenderer.ToggleShowKdTree();
			}
			m_graphicsRenderer.Render();
#if(USE_TIMER == 1)
			lastFrame = thisFrame;
#endif
		}
	}
	return ( int )messageQueue.wParam;
}

void Simulation::m_SwitchToFreeMode( void )
{
	m_simulationMode = FreeMode;
	m_graphicsRenderer.SetCamera(&m_cameraFree);
}

void Simulation::m_SwitchToViewMode( void )
{
	m_simulationMode = ViewMode;
	m_graphicsRenderer.SetCamera(&m_cameraSimulationView);
	m_lastSimulationStart = render_clock_t::now();
	m_kochanekBartelsCurve = KochanekBartelsCurve(m_snapshots);
	m_slerpInterpolator = SlerpInterpolator(m_snapshots);
}

void Simulation::m_BuildKdTree()
{
	std::vector<Model*> models(m_graphicsRenderer.GetRegisteredModels());
	for(std::vector<Model*>::iterator modelIt = models.begin(); modelIt != models.end(); ++modelIt)
	{
		std::vector<TriangleAccessor>* triangleAccessors = (*modelIt)->GetTriangleAccessors();
		// Add current triangleAccessor to list of triangles for building kdTree afterwards
		m_listOfTrianglesForKDTree.insert( m_listOfTrianglesForKDTree.end(),  triangleAccessors->begin(), triangleAccessors->end());
	}

	// Create a list of pointers to TriangleAccessor triangles
	for ( int i = 0; i < m_listOfTrianglesForKDTree.size(); ++i )
	{
		m_listOfTrianglesForKDTree_ptr.push_back( &m_listOfTrianglesForKDTree[i] );
	}

	// Call MakeTree_r and build kdTree recursive
	m_KDTreeRootNode = m_KDTree->MakeTree_r ( m_listOfTrianglesForKDTree_ptr, nullptr, 0, m_listOfTrianglesForKDTree.size(), 0, 3, 0 );

	// Get lines if splitting planes were made
	if ( m_KDTree->FillLineList() )
		m_graphicsRenderer.UpdateKdTree(m_KDTree->GetLineList());
}

void Simulation::m_UpdateRayBasedOnCamera( void )
{
	DirectX::XMFLOAT3 positions[2] = {DirectX::XMFLOAT3(0.f, 0.f, 0.f),
		DirectX::XMFLOAT3(0.f, 0.f, 100000.f)};
	DirectX::XMMATRIX translation = DirectX::XMMatrixInverse(nullptr, m_cameraFree.GetViewMatrix());
	DirectX::XMFLOAT3* rayPositionMember = &m_rayPosition[0];
	for(DirectX::XMFLOAT3* positionActual = &positions[0]; positionActual < &positions[2]; ++positionActual)
	{
		DirectX::XMVECTOR source = DirectX::XMLoadFloat3(positionActual);
		source = DirectX::XMVector3Transform(source, translation);
		DirectX::XMStoreFloat3(positionActual, source);
		*rayPositionMember++ = *positionActual;
	}
	m_ray = LineList(positions, DirectX::XMFLOAT3(1.f, 0.f, 0.f), 2);
	m_graphicsRenderer.UpdateRay(&m_ray);
}

void Simulation::m_HighLightFirstRayTriangleIntersection()
{
	static TriangleAccessor* highlighted = nullptr;
	DirectX::XMFLOAT3 rayOrigin = m_rayPosition[0];
	DirectX::XMFLOAT3 rayDirection(m_rayPosition[1].x - rayOrigin.x,
		m_rayPosition[1].y - rayOrigin.y,
		m_rayPosition[1].z - rayOrigin.z);
	DirectX::XMVECTOR directionVector = DirectX::XMLoadFloat3(&rayDirection);
	directionVector = DirectX::XMVector3Normalize(directionVector);
	DirectX::XMStoreFloat3(&rayDirection, directionVector);
	//TODO: highlight intersected triangle here
	TriangleAccessor* toHighlight = m_KDTree->VisitNodes_r( m_KDTreeRootNode, rayOrigin, rayDirection, 1000.0f );
	if ( toHighlight != nullptr )
	{
		if(highlighted)
		{
			highlighted->HighLight(false);
			m_graphicsRenderer.ApplyVertexChanges(highlighted->GetModelAccessed());
		}
		toHighlight->HighLight(true);
		m_graphicsRenderer.ApplyVertexChanges(toHighlight->GetModelAccessed());
		highlighted = toHighlight;
		RayTriangleIntersection rayTriangleIntersection = toHighlight->IntersectWithRay(rayOrigin, rayDirection);
		assert(rayTriangleIntersection.intersecting);
		m_rayTriangleIntersections.push_back(rayTriangleIntersection);
		while (m_rayTriangleIntersections.size() > 2)
		{
			m_rayTriangleIntersections.pop_front();
		}
		if(m_rayTriangleIntersections.size() == 2)
		{
			XMFLOAT3 positions[2];
			XMVECTOR from = XMLoadFloat3(&m_rayTriangleIntersections.front().intersectionPoint);
			XMVECTOR to = XMLoadFloat3(&m_rayTriangleIntersections.back().intersectionPoint);
			XMVECTOR vector = to - from;
			float distance = XMVector3Length(vector).m128_f32[0];
			CString message;
			message.Format(L"meassured distance between last 2 selected points: %f", distance);
			m_graphicsRenderer.ShowInfo(message);
			XMStoreFloat3(&positions[0], from);
			XMStoreFloat3(&positions[1], to);
			m_measurementLine = LineList(positions, DirectX::XMFLOAT3(0.f, 1.f, 0.f), 2);
			m_graphicsRenderer.UpdateMeasurementLine(&m_measurementLine);
		}
	}
}
